
Imports System.Threading
Imports System.Net.Sockets

'tranditional tcpServer, with a new thread to handle each request
Public Class tcpServer
    Private Shared ReadOnly TCP_CREATE_COUNTER As Int64
    Private Shared ReadOnly TCP_REQUEST_COUNTER As Int64
    Private Shared ReadOnly TCP_RESPONSE_TIMEMS As Int64
    Private _connCount As Int32

    Public Event receiveEx(ByVal handle As talkHandle, ByVal firstConnect As Boolean)
    Public Event receive(ByVal handle As talkHandle)
    Public Const defaultMaxConnCount As Int32 = 1024
    Public Const defaultWaitTime As Int64 = 1
    Public Const defaultHoldTime As Int64 = 30000
    Public Const defaultLiveTime As Int64 = 300000
    <ThreadStatic()> Private Shared _currenthandle As talkHandle = Nothing
    Public livetime As Int64                        'time of a tcp connection live, before resetconnect
    Public holdtime As Int64                        'time of each send and receive
    Public maxConnCount As Int32
    Private _tcpListener As tcpListener2
    Private _port As Integer
    Private _extsignal As Boolean

    Public Shared Function currenthandle() As talkHandle
        assert(Not _currenthandle Is Nothing)
        Return _currenthandle
    End Function

    Public Function active() As Boolean
        Return Not _tcpListener Is Nothing AndAlso _tcpListener.Active()
    End Function

    Public Overridable Function health() As Boolean
        Return active()
    End Function

    Public Overridable Sub [stop](Optional ByVal waittime As Int64 = maxInt64)
        If active() Then
            On Error Resume Next
            _extsignal = True
            If Not _tcpListener Is Nothing Then
                _tcpListener.Server.Shutdown(Net.Sockets.SocketShutdown.Both)
                _tcpListener.Server.Close()
                _tcpListener.Stop()
                _tcpListener = Nothing
            End If
            While (_connCount > 0 AndAlso waittime > 0)
                sleep(defaultWaitTime)
                waittime -= defaultWaitTime
            End While
        End If
    End Sub

    Private Sub initial()
        maxConnCount = defaultMaxConnCount
        holdtime = defaultHoldTime
        livetime = defaultLiveTime
        _extsignal = False
    End Sub

    Public Sub reset()
        [stop]()
        initial()
    End Sub

    Public Function extSignal() As Boolean
        Return _extsignal
    End Function

    Shared Sub New()
        TCP_CREATE_COUNTER = counter.register("TCP_CREATE_COUNTER", flushtimes:=1)
        TCP_REQUEST_COUNTER = counter.register("TCP_REQUEST_COUNTER")
        TCP_RESPONSE_TIMEMS = counter.registerAverageAndLastAverage("TCP_RESPONSE_TIMEMS", _
                                                                    sampleRate:=0.01)
    End Sub

    Public Sub New()
        initial()
    End Sub

    Public Function connCount() As Int32
        Return _connCount
    End Function

    Public Function tcpListener() As tcpListener2
        Return _tcpListener
    End Function

    Public Property port() As Integer
        Get
            Return _port
        End Get
        Set(ByVal value As Integer)
            If Not _tcpListener Is Nothing Then
                reset()
            End If
            _port = value
            _tcpListener = New tcpListener2(Net.IPAddress.Any, _port)
        End Set
    End Property

    Public ReadOnly Property address() As String
        Get
            If Not _tcpListener Is Nothing Then
                Return Net.IPAddress.Any.ToString
            Else
                Return Nothing
            End If
        End Get
    End Property

    Protected Function handlecall(ByVal handle As talkHandle) As Boolean
        assert(Not handle Is Nothing, "should not call handlecall with an invalid talkHandle object.")
        If handle.__receive(False) Then
            Dim handleTCP_REQUEST_COUNTER As Boolean
            handleTCP_REQUEST_COUNTER = (rnd(0, 1) <= 0.01)
            If handleTCP_REQUEST_COUNTER Then
                counter.increase(TCP_REQUEST_COUNTER)
            End If
            handle._blanktime = ticksToMilliseconds(Now.Ticks)
            Try
                'keep compatibility with old codes, if using timer3Solution, _first can be got from handle.
                _currenthandle = handle
                RaiseEvent receiveEx(handle, handle._first)
                RaiseEvent receive(handle)
            Catch ex As Exception
                raiseError("caught unhandled exception, ex " + ex.Message + ", callstack " + ex.StackTrace, _
                           errorHandle.errorType.critical)
            End Try
            _currenthandle = Nothing
            handle._first = False
            counter.increase(TCP_RESPONSE_TIMEMS, ticksToMilliseconds(Now.Ticks()) - handle._blanktime)
            If handleTCP_REQUEST_COUNTER Then
                counter.decrease(TCP_REQUEST_COUNTER)
            End If
            Return True
        Else
            Return False
        End If
    End Function

    Private Sub starthandleImpl(ByVal obj As Object)
        Dim handle As talkHandle = Nothing
        handle = convertor.cast(Of talkHandle)(obj)
        While Not extSignal() AndAlso Not handle.closed()
            If Not handlecall(handle) Then
                sleep(defaultWaitTime)
                If ticksToMilliseconds(Now.Ticks) - handle._blanktime > livetime Then
                    Exit While
                End If
            End If
        End While
        handle.release()
    End Sub

    Protected Overridable Sub starthandle(ByVal handle As talkHandle)
        Dim th As System.Threading.Thread = Nothing
        th = startThread(AddressOf starthandleImpl, handle)
    End Sub

    Protected Overridable Function generatehandleInstance() As talkHandle
        Return New talkHandle()
    End Function

    Private Sub createHandle(ByVal tcpClient As TcpClient)
        Dim handle As talkHandle = Nothing
        handle = generatehandleInstance()
        handle.client = tcpClient
        handle.client.ReceiveTimeout = holdtime
        handle.client.SendTimeout = holdtime
        handle.client.NoDelay = True
        handle._caller = Me
        handle._blanktime = ticksToMilliseconds(Now.Ticks)
        counter.increase(TCP_CREATE_COUNTER)
        Interlocked.Increment(_connCount)
        starthandle(handle)
    End Sub

    Private Sub listen()
        assert(Not _extsignal, "should remove call before set extsignal.")
        assert(Not _tcpListener Is Nothing AndAlso _tcpListener.Active(), _
               "should not call listen when tcplistener is not available.")

        If _tcpListener.Pending() Then
            While _tcpListener.Pending() AndAlso _connCount < maxConnCount
                createHandle(_tcpListener.AcceptTcpClient())
            End While
        End If
    End Sub

    Protected Overridable Sub startListen()
        While Not _extsignal AndAlso _tcpListener.Active
            listen()
            If Not _tcpListener.Pending() Then
                'wait defaultWaitTime is no pending connections
                sleep(defaultWaitTime)
            End If
        End While
        _tcpListener.Stop()
    End Sub

    Public Function start() As Boolean
        On Error Resume Next
        If Not _tcpListener Is Nothing Then
#If Not (PocketPC Or Smartphone) Then
            _tcpListener.Server.SendTimeout = holdtime
            _tcpListener.Server.ReceiveTimeout = holdtime
            _tcpListener.Server.NoDelay = True
#End If
            _tcpListener.Start()
            If _tcpListener.Active() Then
                raiseError("tcpserver started on port " + Convert.ToString(port()))
                startListen()
                Return True
            Else
                Return False
            End If
        Else
            Return False
        End If
    End Function

    Public Function start(ByVal newPort As Integer) As Boolean
        port = newPort
        Return start()
    End Function

    Public Class talkHandle
        Public receiveData As nettransData
        Protected Friend _caller As tcpServer2
        Protected Friend _client As Net.Sockets.TcpClient
        Protected Friend _startTime As Int64
        Protected Friend _blanktime As Int64
        Protected Friend _first As Boolean

        Public Function remoteAddress() As String
            Return Net.IPAddress.Parse(remoteIPEndPoint.Address.ToString).ToString
        End Function

        Public Function remotePort() As Int32
            Return remoteIPEndPoint.Port
        End Function

        Public Function remoteIPEndPoint() As Net.IPEndPoint
            Return convertor.cast(Of Net.IPEndPoint)(_client.Client.RemoteEndPoint)
        End Function

        Protected Friend Function __receive(Optional ByVal needOvertimeInfo As Boolean = True) As Boolean
            'nice to have >= dataSizePrefixLength, avoid the thread pending for an obvious not ready connection
            Return Not closed() AndAlso _client.Client.Available >= tcpTrans.dataSizePrefixLength AndAlso _
                   tcpTrans.receive(_client, receiveData, _caller.holdtime, needOvertimeInfo)
        End Function

        Public Function [return](ByVal data As nettransData) As Boolean
            Dim extCode As Boolean = True
            If tcpTrans.send(_client, data, _caller.holdtime) Then
                extCode = True
            Else
                extCode = False
            End If

            Return extCode
        End Function

        Public Property client() As Net.Sockets.TcpClient
            Get
                Return _client
            End Get
            Set(ByVal value As Net.Sockets.TcpClient)
                tcpTrans.closeTcpClient(_client)
                If Not tcpTrans.clientClosed(value) Then
                    _client = value
                    _client.NoDelay = True
                End If
            End Set
        End Property

        Public Function closed() As Boolean
            Return tcpTrans.clientClosed(_client)
        End Function

        Private Function caller() As tcpServer2
            Return _caller
        End Function

        Public Overridable Sub release()
            On Error Resume Next

            tcpTrans.closeTcpClient(_client)
            receiveData.clear()
            _client = Nothing
            If Not caller() Is Nothing Then
                Interlocked.Decrement(caller()._connCount)
                counter.decrease(tcpServer.TCP_CREATE_COUNTER)
            End If
        End Sub

        Public Overridable Sub initial()
            receiveData = New nettransData()
            _first = True
        End Sub

        Public Sub New()
            initial()
        End Sub

        Public Sub New(ByVal newClient As Net.Sockets.TcpClient)
            initial()
            client = newClient
        End Sub
    End Class
End Class
